/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
    \\  /    A nd           | www.openfoam.com
     \\/     M anipulation  |
-------------------------------------------------------------------------------
    Copyright (C) 2011-2016 OpenFOAM Foundation
    Copyright (C) 2016-2020 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
    This file is part of OpenFOAM.

    OpenFOAM is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    for more details.

    You should have received a copy of the GNU General Public License
    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.

\*---------------------------------------------------------------------------*/

#include "codedBase.H"
#include "SHA1Digest.H"
#include "dynamicCode.H"
#include "dynamicCodeContext.H"
#include "dlLibraryTable.H"
#include "Pstream.H"
#include "PstreamReduceOps.H"
#include "OSspecific.H"
#include "Ostream.H"
#include "regIOobject.H"

// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //

namespace Foam
{
    defineTypeNameAndDebug(codedBase, 0);
}


// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //

namespace Foam
{

//! \cond fileScope
static inline void writeEntryIfPresent
(
    Ostream& os,
    const dictionary& dict,
    const word& key
)
{
    const entry* eptr = dict.findEntry(key, keyType::LITERAL);

    if (eptr)
    {
        const tokenList& toks = eptr->stream();

        if (!toks.empty())
        {
            os.writeEntry(key, toks[0]);
        }
    }
}
//! \endcond

} // End namespace Foam


// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //

void Foam::codedBase::writeCodeDict(Ostream& os, const dictionary& dict)
{
    writeEntryIfPresent(os, dict, "codeInclude");
    writeEntryIfPresent(os, dict, "localCode");
    writeEntryIfPresent(os, dict, "code");
    writeEntryIfPresent(os, dict, "codeOptions");
    writeEntryIfPresent(os, dict, "codeLibs");
}


// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //

void* Foam::codedBase::loadLibrary
(
    const fileName& libPath,
    const std::string& funcName,
    const dynamicCodeContext& context
) const
{
    // Avoid compilation by loading an existing library

    void* handle =
    (
        !libPath.empty() && libs().open(libPath, false)
      ? libs().findLibrary(libPath)
      : nullptr
    );

    if (!handle)
    {
        return handle;
    }

    // Verify the loaded version and unload if needed

    // Manual execution of code after loading.
    // This is mandatory for codedBase.

    void* rawSymbol = dlSymFind(handle, funcName);

    if (rawSymbol)
    {
        loaderType fun = reinterpret_cast<loaderType>(rawSymbol);

        if (fun)
        {
            (*fun)(true);    // force load
        }
        else
        {
            FatalIOErrorInFunction(context.dict())
                << "Failed symbol lookup " << funcName.c_str() << nl
                << "from " << libPath << nl
                << exit(FatalIOError);
        }
    }
    else
    {
        FatalIOErrorInFunction(context.dict())
            << "Failed symbol lookup " << funcName.c_str() << nl
            << "from " << libPath << nl
            << exit(FatalIOError);

        handle = nullptr;
        if (!libs().close(libPath, false))
        {
            FatalIOErrorInFunction(context.dict())
                << "Failed unloading library " << libPath << nl
                << exit(FatalIOError);
        }
    }

    return handle;
}


void Foam::codedBase::unloadLibrary
(
    const fileName& libPath,
    const std::string& funcName,
    const dynamicCodeContext& context
) const
{
    void* handle =
    (
        !libPath.empty() && libs().open(libPath, false)
      ? libs().findLibrary(libPath)
      : nullptr
    );

    if (!handle)
    {
        return;
    }

    // Manual execution of code before unloading.
    // This is mandatory for codedBase.

    void* rawSymbol = dlSymFind(handle, funcName);

    if (rawSymbol)
    {
        loaderType fun = reinterpret_cast<loaderType>(rawSymbol);

        if (fun)
        {
            (*fun)(false);    // force unload
        }
        else
        {
            FatalIOErrorInFunction(context.dict())
                << "Failed symbol lookup " << funcName.c_str() << nl
                << "from " << libPath << nl
                << exit(FatalIOError);
        }
    }

    if (!libs().close(libPath, false))
    {
        FatalIOErrorInFunction(context.dict())
            << "Failed unloading library " << libPath << nl
            << exit(FatalIOError);
    }
}


void Foam::codedBase::createLibrary
(
    dynamicCode& dynCode,
    const dynamicCodeContext& context
) const
{
    bool create =
        Pstream::master()
     || (regIOobject::fileModificationSkew <= 0);   // not NFS

    if (create)
    {
        // Write files for new library
        if (!dynCode.upToDate(context))
        {
            // filter with this context
            dynCode.reset(context);

            this->prepare(dynCode, context);

            if (!dynCode.copyOrCreateFiles(true))
            {
                FatalIOErrorInFunction(context.dict())
                    << "Failed writing files for" << nl
                    << dynCode.libRelPath() << nl
                    << exit(FatalIOError);
            }
        }

        if (!dynCode.wmakeLibso())
        {
            FatalIOErrorInFunction(context.dict())
                << "Failed wmake " << dynCode.libRelPath() << nl
                << exit(FatalIOError);
        }
    }


    // all processes must wait for compile to finish
    if (regIOobject::fileModificationSkew > 0)
    {
        //- Since the library has only been compiled on the master the
        //  other nodes need to pick this library up through NFS
        //  We do this by just polling a few times using the
        //  fileModificationSkew.

        const fileName libPath = dynCode.libPath();

        off_t mySize = Foam::fileSize(libPath);
        off_t masterSize = mySize;
        Pstream::scatter(masterSize);

        for
        (
            label iter = 0;
            iter < regIOobject::maxFileModificationPolls;
            iter++
        )
        {
            DebugPout
                << "on processor " << Pstream::myProcNo()
                << " have masterSize:" << masterSize
                << " and localSize:" << mySize
                << endl;

            if (mySize == masterSize)
            {
                break;
            }
            else if (mySize > masterSize)
            {
                FatalIOErrorInFunction(context.dict())
                    << "Excessive size when reading (NFS mounted) library "
                    << nl << libPath << nl
                    << "on processor " << Pstream::myProcNo()
                    << " detected size " << mySize
                    << " whereas master size is " << masterSize
                    << " bytes." << nl
                    << "If your case is NFS mounted increase"
                    << " fileModificationSkew or maxFileModificationPolls;"
                    << nl << "If your case is not NFS mounted"
                    << " (so distributed) set fileModificationSkew"
                    << " to 0"
                    << exit(FatalIOError);
            }
            else
            {
                DebugPout
                    << "Local file " << libPath
                    << " not of same size (" << mySize
                    << ") as master ("
                    << masterSize << "). Waiting for "
                    << regIOobject::fileModificationSkew
                    << " seconds." << endl;

                Foam::sleep(regIOobject::fileModificationSkew);

                // Recheck local size
                mySize = Foam::fileSize(libPath);
            }
        }


        // Finished doing iterations. Do final check
        if (mySize != masterSize)
        {
            FatalIOErrorInFunction(context.dict())
                << "Cannot read (NFS mounted) library " << nl
                << libPath << nl
                << "on processor " << Pstream::myProcNo()
                << " detected size " << mySize
                << " whereas master size is " << masterSize
                << " bytes." << nl
                << "If your case is NFS mounted increase"
                << " fileModificationSkew or maxFileModificationPolls;"
                << nl << "If your case is not NFS mounted"
                << " (so distributed) set fileModificationSkew"
                << " to 0"
                << exit(FatalIOError);
        }

        DebugPout
            << "on processor " << Pstream::myProcNo()
            << " after waiting: have masterSize:" << masterSize
            << " and localSize:" << mySize << endl;
    }
    reduce(create, orOp<bool>());
}


// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //

void Foam::codedBase::setCodeContext(const dictionary& dict)
{
    context_.setCodeContext(dict);
}


void Foam::codedBase::append(const std::string& str)
{
    context_.append(str);
}


void Foam::codedBase::updateLibrary
(
    const word& name,
    const dynamicCodeContext& context
) const
{
    dynamicCode::checkSecurity
    (
        "codedBase::updateLibrary()",
        context.dict()
    );

    // codeName: name + _<sha1>
    // codeDir : name
    dynamicCode dynCode
    (
        name + context.sha1().str(true),
        name
    );

    const fileName libPath = dynCode.libPath();


    // The correct library was already loaded => we are done
    if (libs().findLibrary(libPath))
    {
        return;
    }

    DetailInfo
        << "Using dynamicCode for " << this->description().c_str()
        << " at line " << context.dict().startLineNumber()
        << " in " << context.dict().name() << endl;


    // Remove instantiation of fvPatchField provided by library
    this->clearRedirect();

    // May need to unload old library
    unloadLibrary
    (
        oldLibPath_,
        dynamicCode::libraryBaseName(oldLibPath_),
        context
    );

    // Try loading an existing library (avoid compilation when possible)
    if (!loadLibrary(libPath, dynCode.codeName(), context))
    {
        createLibrary(dynCode, context);

        loadLibrary(libPath, dynCode.codeName(), context);
    }

    // Retain for future reference
    oldLibPath_ = libPath;
}


void Foam::codedBase::updateLibrary
(
    const word& name,
    const dictionary& dict
) const
{
    updateLibrary(name, dynamicCodeContext(dict));
}


void Foam::codedBase::updateLibrary(const word& name) const
{
    if (context_.valid())
    {
        updateLibrary(name, context_);
    }
    else
    {
        updateLibrary(name, dynamicCodeContext(this->codeDict()));
    }
}


// ************************************************************************* //
